#pragma once

#include "ModelNode.hpp"
#include "../skeleton/Bone.hpp"

class Model : public ModelNode {

	friend class ModelLayer;

private:

	void draw();
	
public:

	mat4 matrix;

private:

	GLuint finalMatrixBuffer = 0;

	std::vector <Bone> boneVector;
	
	void updateFinalMatrix(){
		glBindBuffer(GL_SHADER_STORAGE_BUFFER,finalMatrixBuffer);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, finalMatrixBuffer);
		// немного оптимизации, обновляем только те кости, 
		// которые изменились и их потомков
		for (unsigned int i = 0; i<boneVector.size(); ++i){
			Bone* bn = &boneVector[i];
			//вычисляем трансформационную матрицу если она изменилась
			if (bn->updateFlag) {
				bn->transformMatrix = glm::mat4(
					bn->scale.x,0,0,0,
					0,bn->scale.y,0,0,
					0,0,bn->scale.z,0,
					bn->translate.x,bn->translate.y,bn->translate.z,1
				);
				bn->transformMatrix = bn->transformMatrix * glm::toMat4(bn->rotate);
				bn->transformMatrix = bn->relativeMatrix * bn->transformMatrix;
			}
			// смотрим изменялся ли предок, и если да обновляемся относительно предка
			if (bn->parent!=-1)
				bn->updateFlag = boneVector[bn->parent].updateFlag;
			if (bn->updateFlag){
				bn->finalMatrix = bn->parent!=-1 ? boneVector[bn->parent].finalMatrix * bn->transformMatrix : bn->transformMatrix;
				//заносим финальную матрицу в буфер
				//и задаемся вопросом зачем нам финальная матрица в классе боне
				//немного позже....
				//а не, пригодиться, она иногда домнажается на предка, а тоскать ее
				//с той стороны накладно
				glBufferSubData(GL_SHADER_STORAGE_BUFFER,sizeof(glm::mat4)*i,sizeof(glm::mat4),glm::value_ptr(bn->finalMatrix));
			}
		}
		// снимаем пометки, скелет обновлен
		for (unsigned int i = 0; i<boneVector.size(); ++i)
			boneVector[i].updateFlag = false;
	}

public:

	Bone& bone(unsigned int index);
	
	void updateBones();

	void setBoneCount(unsigned int count);
	
	const unsigned int getBoneCount() const;
	
	Model();
	
	~Model();
	
	bool loadBones(const char* fileName);
	
	void saveBones(const char* fileName);

};

bool Model::loadBones(const char* fileName){
	FILE* f = fopen(fileName,"rb");
	if (f) {
		unsigned int count;
		if (fread(&count,sizeof(count),1,f)!=1){
			fclose(f);
			return false;
		}
		setBoneCount(count);
		for (unsigned int i = 0; i < count; i++){
			if (!boneVector[i].load(f)){
				fclose(f);
				return false;
			}
		}
		return true;
		fclose(f);
	}
	return false;
}
	
void Model::saveBones(const char* fileName){
	FILE* f = fopen(fileName,"wb");
	if (f) {
		unsigned int count = boneVector.size();
		fwrite(&count,sizeof(count),1,f);
		for (unsigned int i = 0; i < count; i++)
			boneVector[i].save(f);
		fclose(f);
	}
}

Model::Model(){
	glGenBuffers(1,&finalMatrixBuffer);
}

Model::~Model(){
	glDeleteBuffers(1,&finalMatrixBuffer);
}

Bone& Model::bone(unsigned int index) {
	return boneVector[index];
}

void Model::setBoneCount(unsigned int count){
	glBindBuffer(GL_SHADER_STORAGE_BUFFER,finalMatrixBuffer);
	glBufferData(GL_SHADER_STORAGE_BUFFER,sizeof(glm::mat4)*boneVector.size(),NULL, GL_DYNAMIC_DRAW);
	boneVector.resize(count);
	for (auto it = boneVector.begin(); it!=boneVector.end(); ++it)
		(*it).update();
}	

const unsigned int Model::getBoneCount() const{
	return boneVector.size();
}

void Model::draw(){
	if (enableFlag) {
		engine.shader.setModelViewMatrix(matrix);
		//updateFinalMatrix();
		ModelNode::draw();
	}
}
